// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <time.h>
#include "CaeRTSAClientSocket.h"
#include "../cae_common/CaeLogConfig.h"
#include "../cae_agent/CaeAgent.h"
#include "../cae_common/CaeMsgCode.h"
#include "CaeEngineControl.h"
#include "CaeMediaConfig.h"

// RTSA接入国家码
const string COUNTRY_CODE = "CN";
// RTSA日志存放路径
const string RTSA_LOG_PATH = "/data/cloudAppEngine/";
// RTSA接入鉴权文件存放路径
const string RTSA_GRS_PATH = "/data/cloudAppEngine/nk-grs/";
const string RTSA_CA_FILE_PATH = "/data/cloudAppEngine/012-DigiCert-Global-Root-CA.cer";

CaeRTSAClientSocket::CaeRTSAClientSocket()
{
    CaeCreateRTSAEngine();
    m_startPublishThread = std::thread(std::bind(&CaeRTSAClientSocket::StartPublishProcess, this));
    m_stopPublishThread = std::thread(std::bind(&CaeRTSAClientSocket::StopPublishProcess, this));
    m_threadRunFlag = true;
}

                                         
void CaeRTSAClientSocket::StartPublishProcess()
{
    while (true) {
        std::unique_lock<std::mutex> lock(m_startPublishLock);
        m_startPublishCond.wait_for(lock, std::chrono::milliseconds(500), [&] {
            return m_startPublishTrigger;
        });

        if (!m_threadRunFlag) {
            break;
        }
        
        if (m_startPublishTrigger) {
            m_startPublishTrigger = false;
            StartPubVideoMainStream();
        }
    }
}

void CaeRTSAClientSocket::StopPublishProcess()
{
    while (true) {
        std::unique_lock<std::mutex> lock(m_stopPublishLock);
        m_stopPublishCond.wait_for(lock, std::chrono::milliseconds(500), [&] {
            return m_stopPublishTrigger;
        });

        if (!m_threadRunFlag) {
            break;
        }
        
        if (m_stopPublishTrigger) {
            m_stopPublishTrigger = false;
            StopPubVideoMainStream();
        }
    }
}

int CaeRTSAClientSocket::CaeCreateRTSAEngine()
{
    std::string appId = CaeConfigManage::GetRTSAAppId();
    std::string appKey = CaeConfigManage::GetRTSAAppKey();
    std::string roomId = CaeConfigManage::GetRTSARoomId();
    std::string userId = CaeConfigManage::GetRTSAUserId();
    LOGI("RTSA init, read from config: appId - %s, roomId - %s, userId - %s",
         appId.c_str(), roomId.c_str(), userId.c_str());
    if (appId == "" || appKey == "" || roomId == "" || userId == "") {
        LOGE("RTSA config error");
        return -1;
    }
    m_rtsaEventHandler = new (std::nothrow) CaeRTSAEventHandler(this);
    if (m_rtsaEventHandler == nullptr) {
        LOGE("new CaeRTSAEventHandler failed");
        return -1;
    }
    huawei::rtsa::HRTSACreateParam createParam;
    memset(&createParam, 0, sizeof(huawei::rtsa::HRTSACreateParam));
    strcpy_s(createParam.appId, huawei::rtsa::RTSA_MAX_APPID_LEN, appId.c_str());
    strcpy_s(createParam.countryCode, huawei::rtsa::RTSA_MAX_COUNTRY_CODE_LEN, COUNTRY_CODE.c_str());
    strcpy_s(createParam.logPath, huawei::rtsa::RTSA_MAX_PATH, RTSA_LOG_PATH.c_str());
    createParam.relayMode = 0;
    createParam.enableEventTracking = true;
    createParam.logFilter = 3;
    if (CaeConfigManage::GetRTSANative()) {
        createParam.transportMode = WEBRTC_0; // native use p2p webrtc
    } else {
        createParam.transportMode = UNITRANS_RELIABLE; // h5 use unitrans->webrtc
    }
    
    m_rtsaEngine = createMediaTransportHRTSAEngine(&createParam, m_rtsaEventHandler);
    if (m_rtsaEngine == nullptr) {
        LOGE("create stream encode engine failed");
        return -1;
    }
    // 设置打点tag
    m_rtsaEngine->setEventTrackingTag("cph");

    huawei::rtsa::HRTSAEncryptionConfig encryptCfg;
    memset(&encryptCfg, 0, sizeof(huawei::rtsa::HRTSAEncryptionConfig));
    encryptCfg.cryptionMode = huawei::rtsa::HRTSACryptionMode::HRTSA_CRYPTO_CUSTOM1;
    m_rtsaEngine->setEncryption(encryptCfg);
    // 设置最大带宽
    huawei::rtsa::HRTSANetworkBandwidth bandwidthCfg;
    memset(&bandwidthCfg, 0, sizeof(huawei::rtsa::HRTSANetworkBandwidth));
    if (CaeConfigManage::GetRTSANative()) {
        bandwidthCfg.maxTotalBandwith = 10240; // native 10Mbps 最大预估带宽
    } else {
        bandwidthCfg.maxTotalBandwith = 40960;
    }
    m_rtsaEngine->setNetworkBandwidth(bandwidthCfg);
    // 开启cmd通道
    m_rtsaEngine->enableCommandMsg(true);
    
    huawei::rtsa::HRTSAInitConfig initConfig;
    memset(&initConfig, 0, sizeof(huawei::rtsa::HRTSAInitConfig));
    strcpy_s(initConfig.grsRootPath, huawei::rtsa::RTSA_MAX_PATH, RTSA_GRS_PATH.c_str());
    strcpy_s(initConfig.caFilePath, huawei::rtsa::RTSA_MAX_PATH, RTSA_CA_FILE_PATH.c_str());
    if (m_rtsaEngine->setInitConfig(initConfig) != 0) {
        LOGE("rtsa engine set init config failed ");
        return -1;
    }

    huawei::rtsa::HRTSAJoinParam joinParam;
    long long ctime = 0;
    std::string token = "";
    token = GetSignature(roomId.c_str(), userId.c_str(), &ctime, appId.c_str(), appKey.c_str());
    strcpy_s(joinParam.appId, huawei::rtsa::RTSA_MAX_APPID_LEN, appId.c_str());
    strcpy_s(joinParam.token, huawei::rtsa::RTSA_MAX_SIGNATURE_LEN, token.c_str());
    strcpy_s(joinParam.userId, huawei::rtsa::RTSA_MAX_USERID_LEN, userId.c_str());
    strcpy_s(joinParam.roomId, huawei::rtsa::RTSA_MAX_ROOMID_LEN, roomId.c_str());
    joinParam.ctime = ctime;
    if (m_rtsaEngine->joinRoom(joinParam) != 0) {
        LOGE("appId: %s, userId: %s, roomId: %s joinRoom failed", appId.c_str(), userId.c_str(), roomId.c_str());
        return -1;
    }

    LOGI("appId: %s, userId: %s, roomId: %s joinRoom called", appId.c_str(), userId.c_str(), roomId.c_str());
    return 0;
}


CaeRTSAClientSocket::~CaeRTSAClientSocket()
{
    // 释放启停推流线程
    m_threadRunFlag = false;
    if (m_startPublishThread.joinable()) {
        m_startPublishThread.join();
    }
    if (m_stopPublishThread.joinable()) {
        m_stopPublishThread.join();
    }
    // 离开房间
    LeaveRoom();
    // 销毁引擎和回调处理对象
    if (m_rtsaEngine) {
        m_rtsaEngine->destory();
        m_rtsaEngine = nullptr;
    }
    if (m_rtsaEventHandler) {
        delete m_rtsaEventHandler;
        m_rtsaEventHandler = nullptr;
    }
}

int CaeRTSAClientSocket::Send(void *pkt, size_t size)
{
    // 合法性校验
    size_t msgHeaderSize = sizeof(STREAM_MSG_HEAD);
    if (pkt == nullptr || size < msgHeaderSize) {
        LOGE("RTSA send check, packet nullptr or wrong size: %d", size);
        return SOCKET_SEND_FAIL_RETRY;
    }
    // 判断数据包类型
    StreamMsgHead* msgHead = static_cast<StreamMsgHead*>(pkt);
    uint8_t msgType = msgHead->type;
    uint8_t* dataPtr = static_cast<uint8_t*>(pkt);

    // Attention: 调用音频/视频发送时，需要去除Tcp的StreamMsgHead包头；而Cmd发送不去除该包头
    if (msgType == CAE_MSG_AUDIO) {
        return SendAudioData(dataPtr + msgHeaderSize, size - msgHeaderSize);
    } else if (msgType == CAE_MSG_VIDEO) {
        return SendVideoData(dataPtr + msgHeaderSize, size - msgHeaderSize);
    } else {
        // 其余所有数据类型都默认使用Rtsa Cmd接口发送
        return SendCmdData(dataPtr, size, msgType);
    }
}

int CaeRTSAClientSocket::Recv(void *pkt, size_t size)
{
    // RTSA接收为订阅流模式，不使用主动调用流程
    return 0;
}

void CaeRTSAClientSocket::CloseSocket()
{
    // 暂时停止Socket流程时不对Rtsa做具体操作，需要在应用生命周期内保持入会状态，以确保用户重进成功
    LOGI("RTSA CloseSocket process, do nothing.");
}

void CaeRTSAClientSocket::OnJoinRoomSuccess(const char *userId, const char *roomId, int elapsed)
{
    LOGI("RTSA OnJoinRoomSuccess, userId %s, roomId %s", userId, roomId);
    m_bJoinRoomSuccess = true;
    StartPubAudioStream();

    // 获取打点reporter
    if (!CaeConfigManage::GetRTSANative()) {
        m_reporter = huawei::er::getEventRepoter(reinterpret_cast<std::uintptr_t>(m_rtsaEventHandler));
    }
}

void CaeRTSAClientSocket::OnRemoteUserOnline(const char *userId)
{
    LOGI("RTSA OnRemoteUserOnline, userId: %s.", userId);
    {
        std::unique_lock<std::mutex> lock(m_startPublishLock);
        m_startPublishTrigger = true;
        m_startPublishCond.notify_all();
    }
    CaeSocketAgent::GetInstance().SetClientSocket(this);
}

void CaeRTSAClientSocket::OnRemoteUserOffline(const char *userId, int reason)
{
    LOGI("RTSA OnRemoteUserOffline, userId: %s, reasion: %d", userId, reason);
    {
        std::unique_lock<std::mutex> lock(m_stopPublishLock);
        m_stopPublishTrigger = true;
        m_stopPublishCond.notify_all();
    }
    // Notify that the client socket has disconnected
    CaeAgent::GetInstance().CloseClient();
    this->EventNotice();
}

void CaeRTSAClientSocket::OnRemoteCommandPacket(const char *userId, const unsigned char *packet,
    unsigned int length)
{
    CaeSocketAgent::GetInstance().RecvCmdFromRTSA((void*)(packet), length);
}

void CaeRTSAClientSocket::OnRequestKeyFrame(const char *userId, const huawei::rtsa::HRTSAStreamType streamType)
{
    LOGI("RTSA OnRequestKeyFrame");
    Result res = CaeEngineControl::GetInstance().RequestIframe();
    if (res != SUCCESS) {
        LOGE("Call Engine Control RequestIFrame Failed.");
    }
}

void CaeRTSAClientSocket::OnLocalVideoBitrateUpdate(const huawei::rtsa::HRTSAStreamType streamType, unsigned int targetEncBitrate)
{
    // 动态码率更新
    if (m_lastEstimateBitrate == targetEncBitrate) {
        return;
    }
    m_lastEstimateBitrate = targetEncBitrate;

    if ((targetEncBitrate == 0 || targetEncBitrate < 1000) &&
        !CaeConfigManage::GetRTSANative()) {
        return;
    }
    
    if (CaeConfigManage::GetRTSANative()) {
        targetEncBitrate = targetEncBitrate * 1000; 
    } else {
        targetEncBitrate = targetEncBitrate * 1000 * 0.85;
    }
    // 码率最大值使用码表设定的码率
    targetEncBitrate = std::min(targetEncBitrate, CaeMediaConfig::GetInstance().GetCmdBitrate());
    
    if (m_lastEncodeBitrate == targetEncBitrate) {
        return;
    }
    m_lastEncodeBitrate = targetEncBitrate;
    char bitrate[20] = {0};
    sprintf_s(bitrate, 20, "%d", targetEncBitrate);
    CaeMediaConfig::GetInstance().SetBitrate(bitrate, false);
    Result res = CaeEngineControl::GetInstance().SetMediaConfig();
    if (res != SUCCESS) {
        LOGE("Call Engine Control SetMediaConfig Failed.");
    }
}

void CaeRTSAClientSocket::OnLocalVideoStatsNotify(const huawei::rtsa::UpNetworkVideoStatics* connectionStatics,
        const huawei::rtsa::VideoSendStaticsInfo* videoSendStatics)
{
    if (m_reporter) {
        RTSA_V_EVENT videoUpEvent{ 0 };
	    auto* vp = &videoUpEvent;
        vp->sendStatisticSize = 1;
        //AvgDelayMs
        vp->sendStatistic[0].sendRtt = connectionStatics->avgdelay;;
        //NetSendBitrateMbps
        vp->sendStatistic[0].packetRate = connectionStatics->curSendBitRate / 1000.0f;
        //estimate_bit_rate Mbps
        vp->sendStatistic[0].remoteLostRate = m_lastEstimateBitrate / 1000.0f;
        //StreamFps
        vp->sendStatistic[0].sendFrameRate = videoSendStatics->sendStatics[0].frameRate;
        //CaptureLatencyMs
        vp->sendStatistic[0].availableEncBitrate = CaeEngineControl::GetInstance().GetCaptureLatency();
        //EncoderLatencyMs
        vp->sendStatistic[0].divideFrameDelay = CaeEngineControl::GetInstance().GetEncodeLatency();
        //target encode bitrate Mbps
        vp->sendStatistic[0].estSendBitrate = m_lastEncodeBitrate / 1000000.0f;       
        m_reporter->addVideoStreamUpEvent(vp);
    }
}

void CaeRTSAClientSocket::OnLocalAudioStatsNotify(const huawei::rtsa::AudioSendStaticsInfo* audioSendStatics)
{
    if (m_reporter) {
        RTSA_A_EVENT audioUpEvent{ 0 };
        auto* ap = &audioUpEvent;
        ap->rtt = audioSendStatics->rtt;
        ap->pktSent = audioSendStatics->pktSent;
        ap->pktLoss = audioSendStatics->pktLoss;
        ap->sendBitRate = audioSendStatics->sendBiteRate;
        m_reporter->addAudioStreamUpEvent(ap);
    }
}

int CaeRTSAClientSocket::SendVideoData(uint8_t *data, size_t size)
{
    if (!m_videoEnable) {
        return 0;
    }
    
    if (m_rtsaEngine == nullptr) {
        LOGE("RTSA IHRTSAEngine is nullptr.");
        return -1;
    }
    // 获取当前的视频配置项。暂时放这里，因为media的初始化在socket之后
    uint32_t fps = CaeMediaConfig::GetInstance().GetFrameRate();
    uint32_t width = CaeMediaConfig::GetInstance().GetStreamWidth();
    uint32_t height = CaeMediaConfig::GetInstance().GetStreamHeight();
    if (m_fps != fps) {
        LOGW("RTSA update fps from %u to %u", m_fps, fps);
        m_fps = fps;
    }
    if (m_width != width) {
        LOGW("RTSA update width from %u to %u", m_width, width);
        m_width = width;
    }
    if (m_height != height) {
        LOGW("RTSA update height from %u to %u", m_height, height);
        m_height = height;
    }

    // 计算RTP时间戳
    struct timeval time_now {};
    gettimeofday(&time_now, nullptr);
    uint32_t timestampMs = (time_now.tv_sec * 1000) + (time_now.tv_usec / 1000);
    uint32_t timestampRtp = timestampMs * kMsToRtpTimestamp;

    // 从h264裸流中获取key帧信息
    bool bKeyFrame = false;
    huawei::rtsa::HRTSAVideoFrameType streamType;
    if (data[4] == 0x65 || data[4] == 0x67 || data[4] == 0x68) {
        streamType = huawei::rtsa::HRTSA_VIDEO_VIDEO_FRAME_KEY;
        bKeyFrame = true;
    } else if ((data[4] & 0x60) > 0) {
        streamType = huawei::rtsa::HRTSA_VIDEO_VIDEO_FRAME_ALTREF;
    } else if ((data[4] & 0x60) == 0) {
        streamType = huawei::rtsa::HRTSA_VIDEO_VIDEO_FRAME_DELTA;
    }

    // 生成发送前的结构
    huawei::rtsa::HRTSAFrameVideoOption frameVideoOption{};
    frameVideoOption.uiWidth = m_width;
    frameVideoOption.uiHeight = m_height;
    frameVideoOption.timeSample = timestampRtp;
    frameVideoOption.frameType = streamType;
    if (bKeyFrame) {
        // I帧，参考TS为0
        frameVideoOption.analysisInfo.layerId = 3;
        frameVideoOption.analysisInfo.refFrameTs = 0;
    } else {
        // 基础P帧，参考TS为前一帧的TS
        frameVideoOption.analysisInfo.layerId = 2;
        frameVideoOption.analysisInfo.refFrameTs = m_lastVFrameTimestamp;
    }

    m_lastVFrameTimestamp = timestampRtp;
    m_videoFrameNum++;
    // 发送视频数据包
    int ret = m_rtsaEngine->sendVideoFrameData(
        huawei::rtsa::HRTSAStreamType::HRTSA_STREAM_TYPE_MAIN, data,
        size, frameVideoOption);
    if (ret != 0) {
        LOGE("RTSA call sendVideoFrameData failed, reason: %d", ret);
        return -1;
    }

    return size;
}

int CaeRTSAClientSocket::SendAudioData(uint8_t *data, size_t size)
{
    if (!m_audioEnable) {
        return 0;
    }
    if (m_rtsaEngine == nullptr) {
        LOGE("RTSA IHRTSAEngine is nullptr.");
        return -1;
    }
    uint64_t pts = m_audioFrameNum * kAudioNumSamplesPerFrame;
    huawei::rtsa::HRTSAFrameAudioOption frameAudioOption;
    frameAudioOption.volume = 100;
    frameAudioOption.globalTimeStamp = pts;
    m_audioFrameNum++;
    int ret = m_rtsaEngine->sendAudioFrameData(data, size, frameAudioOption);
    if (ret != 0) {
        LOGE("RTSA call sendAudioFrameData failed");
        return -1;
    }

    return size;
}

int CaeRTSAClientSocket::SendCmdData(uint8_t* data, size_t size, int cmdType)
{
    if (m_rtsaEngine == nullptr) {
        LOGE("RTSA IHRTSAEngine is nullptr.");
        return -1;
    }
    
    int ret = m_rtsaEngine->sendCommandData(nullptr, data, size);
    if (ret != 0) {
        LOGE("RTSA send cmd failed, reason: %d", ret);
        return ret;
    }
    return size;
}

int CaeRTSAClientSocket::StartPubVideoMainStream()
{
    if (m_rtsaEngine == nullptr || m_videoEnable) {
        LOGE("RTSA IHRTSAEngine is nullptr or already start");
        return -1;
    }
    // 当前仅支持推送主视频流
    LOGI("RTSA starts publishing video main stream.");
    int ret = m_rtsaEngine->startLocalVideoStream(huawei::rtsa::HRTSAStreamType::HRTSA_STREAM_TYPE_MAIN);
    if (ret != 0) {
        LOGE("Failed! Reason: %d", ret);
        return ret;
    }
    m_videoEnable = true;
    return 0;
}

int CaeRTSAClientSocket::StopPubVideoMainStream()
{
    if (m_rtsaEngine == nullptr || !m_videoEnable) {
        LOGE("RTSA IHRTSAEngine is nullptr or already stop");
        return -1;
    }
    // 当前仅支持推送主视频流
    LOGI("RTSA stop publishing video main stream.");
    int ret = m_rtsaEngine->stopLocalVideoStream(huawei::rtsa::HRTSAStreamType::HRTSA_STREAM_TYPE_MAIN);
    if (ret != 0) {
        LOGE("Failed! Reason: %d", ret);
        return ret;
    }
    m_videoEnable = false;
    return 0;
}


int CaeRTSAClientSocket::StartPubAudioStream()
{
    if (m_rtsaEngine == nullptr) {
        LOGE("RTSA IHRTSAEngine is nullptr!");
        return -1;
    }
    LOGI("RTSA starts publishing audio stream.");
    int ret = m_rtsaEngine->startLocalAudioStream();
    if (ret != 0) {
        LOGE("Failed! Reason: %d", ret);
        return ret;
    }
    m_audioEnable = true;
    return 0;
}

int CaeRTSAClientSocket::LeaveRoom()
{
    if (m_rtsaEngine == nullptr) {
        LOGE("IHRTSAEngine is nullptr!");
        return -1;
    }
    // 停止视频流
    if (m_videoEnable) {
        m_rtsaEngine->stopLocalVideoStream(huawei::rtsa::HRTSAStreamType::HRTSA_STREAM_TYPE_MAIN);
        LOGI("RTSA local video stream stopped.");
    }
    // 停止音频流
    if (m_audioEnable) {
        m_rtsaEngine->stopLocalAudioStream();
        LOGI("RTSA local audio stream stopped.");
    }
    // 退出房间
    if (m_bJoinRoomSuccess) {
        m_rtsaEngine->leaveRoom();
        LOGI("RTSA leaveRoom.");
        m_bJoinRoomSuccess = false;
    }
    return 0;
}

void HMACSHA256::sha256_transf(const unsigned char *message, unsigned int block_nb)
{
    unsigned int w[64];
    unsigned int wv[8];
    unsigned int t1, t2;
    const unsigned char *sub_block;
    int i;

    int j;

    unsigned int sha256_k[64] = {
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
        0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
        0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
        0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
        0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
        0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
        0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
    };

    for (i = 0; i < (int)block_nb; i++) {
        sub_block = message + (i << 6);

        for (j = 0; j < 16; j++) {
            PACK32(&sub_block[j << 2], &w[j]);
        }

        for (j = 16; j < 64; j++) {
            SHA256_SCR(j);
        }

        for (j = 0; j < 8; j++) {
            wv[j] = m_raw_hash[j];
        }

        for (j = 0; j < 64; j++) {
            t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j];
            t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);
            wv[7] = wv[6];
            wv[6] = wv[5];
            wv[5] = wv[4];
            wv[4] = wv[3] + t1;
            wv[3] = wv[2];
            wv[2] = wv[1];
            wv[1] = wv[0];
            wv[0] = t1 + t2;
        }

        for (j = 0; j < 8; j++) {
            m_raw_hash[j] += wv[j];
        }
    }
}

void HMACSHA256::sha256(const unsigned char *message, unsigned int len, unsigned char *digest)
{
    sha256_init();
    sha256_update(message, len);
    sha256_final(digest);
}

void HMACSHA256::sha256_init()
{
    int i;
    unsigned int sha256_h0[8] = {
        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
        0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    };

    for (i = 0; i < 8; i++) {
        m_raw_hash[i] = sha256_h0[i];
    }

    m_current_len = 0;
    m_total_len = 0;
}

void HMACSHA256::sha256_update(const unsigned char *message, unsigned int dataLen)
{
    unsigned int tmp_len = SHA256_BLOCK_SIZE - m_current_len;
    unsigned int rem_len = dataLen < tmp_len ? dataLen : tmp_len;

    memcpy(&m_hash_block[m_current_len], message, rem_len);

    if (m_current_len + dataLen < SHA256_BLOCK_SIZE) {
        m_current_len += dataLen;
        return;
    }

    unsigned int new_len = dataLen - rem_len;
    unsigned int block_nb = new_len / SHA256_BLOCK_SIZE;

    const unsigned char *shifted_message = message + rem_len;

    sha256_transf(m_hash_block, 1);
    sha256_transf(shifted_message, block_nb);

    rem_len = new_len % SHA256_BLOCK_SIZE;

    memcpy(m_hash_block, &shifted_message[block_nb << 6], rem_len);

    m_current_len = rem_len;
    m_total_len += (block_nb + 1) << 6;
}

void HMACSHA256::sha256_final(unsigned char *digest)
{
    unsigned int block_nb;
    unsigned int pm_len;
    unsigned int len_b;

    int i;
    block_nb = (1 + ((SHA256_BLOCK_SIZE - 9) < (m_current_len % SHA256_BLOCK_SIZE)));

    len_b = (m_total_len + m_current_len) << 3;
    pm_len = block_nb << 6;

    memset(m_hash_block + m_current_len, 0, pm_len - m_current_len);
    m_hash_block[m_current_len] = 0x80;
    UNPACK32(len_b, m_hash_block + pm_len - 4);

    sha256_transf(m_hash_block, block_nb);

    for (i = 0; i < 8; i++) {
        UNPACK32(m_raw_hash[i], &digest[i << 2]);
    }
}

std::string HMACSHA256::getHash(unsigned char* rawHash)
{
    std::string result;
    result.reserve(2 * SHA256_DIGEST_SIZE);
    for (int i = 0; i < SHA256_DIGEST_SIZE; i++)
    {
        static const char dec2hex[16 + 1] = "0123456789abcdef";
        result += dec2hex[(rawHash[i] >> 4) & 15];
        result += dec2hex[rawHash[i] & 15];
    }

    return result;
}

std::string HMACSHA256::HMAC_SHA256(std::string msg, std::string key)
{
    unsigned char Key0[SHA256_BLOCK_SIZE];
    unsigned char* Key0_ipad_concat_text = new unsigned char[SHA256_BLOCK_SIZE + msg.size()];
    unsigned char Key0_ipad_concat_text_digest[SHA256_BLOCK_SIZE];
    unsigned char Key0_opad_concat_prev[SHA256_BLOCK_SIZE + SHA256_DIGEST_SIZE];

    unsigned char HMAC_SHA256[SHA256_DIGEST_SIZE];

    int kyeSize = key.size();
    if (kyeSize <= SHA256_BLOCK_SIZE) {
        for (int i = 0; i < SHA256_BLOCK_SIZE; i++) {
            if (i < kyeSize) Key0[i] = key[i];
            else Key0[i] = 0x00;
        }
    }
    else if (kyeSize > SHA256_BLOCK_SIZE) {
        sha256((const unsigned char*)key.c_str(), kyeSize, Key0);
        for (unsigned char i = SHA256_DIGEST_SIZE; i < SHA256_BLOCK_SIZE; i++) {
            Key0[i] = 0x00;
        }
    }

    for (int i = 0; i < SHA256_BLOCK_SIZE; i++) {
        Key0_ipad_concat_text[i] = Key0[i] ^ 0x36;
        Key0_opad_concat_prev[i] = Key0[i] ^ 0x5C;
    }

    int size = SHA256_BLOCK_SIZE + msg.size();
    for (int i = SHA256_BLOCK_SIZE; i < size; i++) {
        Key0_ipad_concat_text[i] = msg[i - SHA256_BLOCK_SIZE];
    }

    sha256(Key0_ipad_concat_text, SHA256_BLOCK_SIZE + msg.size(), Key0_ipad_concat_text_digest);
    for (int i = SHA256_BLOCK_SIZE; i < SHA256_BLOCK_SIZE + SHA256_DIGEST_SIZE; i++) {
        Key0_opad_concat_prev[i] = Key0_ipad_concat_text_digest[i - SHA256_BLOCK_SIZE];
    }
    delete[] Key0_ipad_concat_text; // clear
    sha256(Key0_opad_concat_prev, SHA256_BLOCK_SIZE + SHA256_DIGEST_SIZE, HMAC_SHA256);
    return getHash(HMAC_SHA256);
}

std::string CaeRTSAClientSocket::GetSignatureLocal(const char* appid, const char* roomid, const char* userid, long long timestamp, const char* key)
{
    std::string date = std::to_string(timestamp);
    const int contentSize = strlen(roomid) + strlen(userid) + strlen(appid) + date.size() + 16;
    char* content = new char[contentSize];
    int contentLen = sprintf_s(content, contentSize, "%s+%s+%s+%s", appid, roomid, userid, date.c_str());

    std::string data = std::string(content, contentLen);
    delete[] content;

    HMACSHA256 sha256;
    return sha256.HMAC_SHA256(data, std::string(key));
}

std::string CaeRTSAClientSocket::GetSignature(const char* roomid, const char* userid, long long* ctime, const char* appid, const char* key)
{
    time_t myt = time(NULL);
    time_t t;
    time(&t);
    long long timestamp = t;
    timestamp = t;
    timestamp = timestamp + MAX_SIGNATURE_EXPIRE_TIME_OUT;
    *ctime = timestamp;
	std::string sign = GetSignatureLocal(appid, roomid, userid, timestamp, key);
    // if local is empty, use remote signature
    if (!sign.empty()) {
        return sign;
    }
    return "";
}
